using System;
using System.Diagnostics;

namespace Dida
{
	/// <summary>
	/// A 3D line segment.
	/// </summary>
	public struct Line3D
	{
		public Vector3D start;
		public Vector3D end;

		/// <summary>
		/// Creates a line from the given parameters.
		/// </summary>
		/// <param name="start">The initial start point of the line.</param>
		/// <param name="end">The initial end point of the line.</param>
		public Line3D(Vector3D start, Vector3D end)
		{
			this.start = start;
			this.end = end;
		}

		/// <summary>
		/// Creates a line from the given parameters
		/// </summary>
		/// <param name="startx">The initial x coordinate of the start point.</param>
		/// <param name="starty">The initial y coordinate of the start point.</param>
		/// <param name="startz">The initial z coordinate of the start point.</param>
		/// <param name="endx">The initial x coordinate of the end point.</param>
		/// <param name="endy">The initial y coordinate of the end point.</param>
		/// <param name="endz">The initial z coordinate of the end point.</param>
		public Line3D(float startx, float starty, float startz,
			float endx, float endy, float endz)
		{
			start = new Vector3D(startx, starty, startz);
			end = new Vector3D(endx, endy, endz);
		}

		/// <summary>
		/// Gets the bounding box of this line. The bounding box is the
		/// smallest box that encloses the whole line.
		/// </summary>
		/// <seealso cref="BoundingSphere"/>
		public Box BoundingBox
		{
			get
			{
				return new Box(
					Math.Min(start.x, end.x),
					Math.Min(start.y, end.y),
					Math.Min(start.z, end.z),
					Math.Max(start.x, end.x),
					Math.Max(start.y, end.y),
					Math.Max(start.z, end.z));
			}
		}

		/// <summary>
		/// Gets the bounding sphere of this line. The bounding sphere is the
		/// smallest sphere that encloses the whole line.
		/// </summary>
		/// <seealso cref="BoundingBox"/>
		public Sphere BoundingSphere
		{
			get
			{
				return new Sphere((start + end) * .5f, (start - end).Len * .5f);
			}
		}

		/// <summary>
		/// <para>
		/// Returns the <i>interpolation parameter</i> of the point on this line
		/// that's the closest to the point in pt.
		/// </para>
		/// <para>
		/// The <i>interpolation parameter</i> specifies where the point lays on
		/// the line, where a value of 0 means the start vertex, a value 
		/// of 1 means the end vertex, and any value in between means a 
		/// point on the line, that can be found by linearily 
		/// interpolation between the start and end vertex. 
		/// </para>
		/// </summary>
		/// <param name="pt">The point, to which you want to find the closest point.</param>
		/// <param name="clamp">Specifies whether the result should be clamped 
		/// to the 0-1 range. If you use clamping, the result will be 
		/// the param of the closest point on the actual line segment. When 
		/// you don't use clamping, the result will be the closest point
		/// on the infinite line of this segment.</param>
		/// <returns>The interpolation parameter of the closest point.</returns>
		/// <seealso cref="GetClosestPoint"/>
		public float GetClosestPointParam(Vector3D pt,bool clamp)
		{
			Vector3D invDir = end - start;
			invDir /= invDir.SqrLen;

			float ret = Vector3D.DotProduct(invDir, pt - start);
			if (clamp) 
			{
				if (ret < 0)
					return 0;
				else if (ret > 1)
					return 1;
				else
					return ret;
			}
			else
				return ret;
		}

		/// <summary>
		/// Gets the point on this line (segment) that's the closest to the point in pt.
		/// </summary>
		/// <param name="pt">The point to which you want to find the closest point.</param>
		/// <returns>The point that's the closest to pt.</returns>
		/// <seealso cref="GetClosestPointParam(Vector3D,bool)"/>
		/// <seealso cref="GetPointAtParam"/>
		public Vector3D GetClosestPoint(Vector3D pt)
		{
			return Vector3D.Interpolate(start, end, GetClosestPointParam(pt, true));
		}

		public float GetClosestPointParam(Ray b,bool clamp)
		{
			// orthogonally project the whole situation to 2D, 
			// so that the ray becomes a point in 2D.

			Vector3D xVec, yVec;
			b.dir.GetPerpVectors(out xVec, out yVec);

			Vector2D b2D = new Vector2D(Vector3D.DotProduct(b.start, xVec),
				Vector3D.DotProduct(b.start, yVec));

			Vector2D start2D = new Vector2D(Vector3D.DotProduct(start, xVec),
				Vector3D.DotProduct(start, yVec));
			Vector2D end2D = new Vector2D(Vector3D.DotProduct(end, xVec),
				Vector3D.DotProduct(end, yVec));

			Vector2D invLineVec = end2D - start2D;
			invLineVec /= invLineVec.SqrLen;
			float res = Vector2D.DotProduct(b2D - start2D, invLineVec);
			if (clamp)
			{
				if (res < 0)
					return 0;
				else if (res > 1)
					return 1;
				else
					return res;
			}
			else
				return res;
		}

		public Vector3D GetPointAtParam(float param)
		{
			return Vector3D.Interpolate(start, end, param);
		}
	}
}